/*
 * Copyright (C) 2005 - 2013 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008 - 2013 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2006 - 2013 ScriptDev2 <http://www.scriptdev2.com/>
 *
 * Copyright (C) 2010 - 2013 ProjectSkyfire <http://www.projectskyfire.org/>
 *
 * Copyright (C) 2011 - 2013 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/* ScriptData
 Name: account_commandscript
 %Complete: 100
 Comment: All account related commands
 Category: commandscripts
 EndScriptData */

#include "ScriptMgr.h"
#include "AccountMgr.h"
#include "Chat.h"

class account_commandscript: public CommandScript
{
public:
    account_commandscript () :
            CommandScript("account_commandscript")
    {
    }

    ChatCommand* GetCommands () const
    {
        static ChatCommand accountSetCommandTable[] =
        {
        { "addon", SEC_ADMINISTRATOR, true, &HandleAccountSetAddonCommand, "", NULL },
        { "gmlevel", SEC_CONSOLE, true, &HandleAccountSetGmLevelCommand, "", NULL },
        { "password", SEC_CONSOLE, true, &HandleAccountSetPasswordCommand, "", NULL },
        { NULL, 0, false, NULL, "", NULL } };
        static ChatCommand accountCommandTable[] =
        {
        { "addon", SEC_MODERATOR, false, &HandleAccountAddonCommand, "", NULL },
        { "create", SEC_CONSOLE, true, &HandleAccountCreateCommand, "", NULL },
        { "delete", SEC_CONSOLE, true, &HandleAccountDeleteCommand, "", NULL },
        { "onlinelist", SEC_CONSOLE, true, &HandleAccountOnlineListCommand, "", NULL },
        { "lock", SEC_PLAYER, false, &HandleAccountLockCommand, "", NULL },
        { "set", SEC_ADMINISTRATOR, true, NULL, "", accountSetCommandTable },
        { "password", SEC_PLAYER, false, &HandleAccountPasswordCommand, "", NULL },
        { "", SEC_PLAYER, false, &HandleAccountCommand, "", NULL },
        { NULL, 0, false, NULL, "", NULL } };
        static ChatCommand commandTable[] =
        {
        { "account", SEC_PLAYER, true, NULL, "", accountCommandTable },
        { NULL, 0, false, NULL, "", NULL } };
        return commandTable;
    }

    static bool HandleAccountAddonCommand (ChatHandler* handler, const char* args)
    {
        if (!*args)
        {
            handler->SendSysMessage(LANG_CMD_SYNTAX);
            handler->SetSentErrorMessage(true);
            return false;
        }

        char *szExp = strtok((char*) args, " ");

        uint32 account_id = handler->GetSession()->GetAccountId();

        int expansion = atoi(szExp);          //get int anyway (0 if error)
        if (expansion < 0 || uint8(expansion) > sWorld->getIntConfig(CONFIG_EXPANSION))
        {
            handler->SendSysMessage(LANG_IMPROPER_VALUE);
            handler->SetSentErrorMessage(true);
            return false;
        }

        // No SQL injection
        LoginDatabase.PExecute("UPDATE account SET expansion = '%d' WHERE id = '%u'", expansion, account_id);
        handler->PSendSysMessage(LANG_ACCOUNT_ADDON, expansion);
        return true;
    }

    /// Create an account
    static bool HandleAccountCreateCommand (ChatHandler* handler, const char* args)
    {
        if (!*args)
            return false;

        ///- %Parse the command line arguments
        char *szAcc = strtok((char*) args, " ");
        char *szPassword = strtok(NULL, " ");
        if (!szAcc || !szPassword)
            return false;

        // normalized in sAccountMgr->CreateAccount
        std::string account_name = szAcc;
        std::string password = szPassword;

        AccountOpResult result = sAccountMgr->CreateAccount(account_name, password);
        switch (result)
        {
        case AOR_OK:
            handler->PSendSysMessage(LANG_ACCOUNT_CREATED, account_name.c_str());
            break;
        case AOR_NAME_TOO_LONG:
            handler->SendSysMessage(LANG_ACCOUNT_TOO_LONG);
            handler->SetSentErrorMessage(true);
            return false;
        case AOR_NAME_ALREDY_EXIST:
            handler->SendSysMessage(LANG_ACCOUNT_ALREADY_EXIST);
            handler->SetSentErrorMessage(true);
            return false;
        case AOR_DB_INTERNAL_ERROR:
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_CREATED_SQL_ERROR, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        default:
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_CREATED, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        }

        return true;
    }

    /// Delete a user account and all associated characters in this realm
    /// \todo This function has to be enhanced to respect the login/realm split (delete char, delete account chars in realm, delete account chars in realm then delete account
    static bool HandleAccountDeleteCommand (ChatHandler* handler, const char* args)
    {
        if (!*args)
            return false;

        ///- Get the account name from the command line
        char *account_name_str = strtok((char*) args, " ");
        if (!account_name_str)
            return false;

        std::string account_name = account_name_str;
        if (!AccountMgr::normalizeString(account_name))
        {
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        }

        uint32 account_id = sAccountMgr->GetId(account_name);
        if (!account_id)
        {
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        }

        /// Commands not recommended call from chat, but support anyway
        /// can delete only for account with less security
        /// This is also reject self apply in fact
        if (handler->HasLowerSecurityAccount(NULL, account_id, true))
            return false;

        AccountOpResult result = sAccountMgr->DeleteAccount(account_id);
        switch (result)
        {
        case AOR_OK:
            handler->PSendSysMessage(LANG_ACCOUNT_DELETED, account_name.c_str());
            break;
        case AOR_NAME_NOT_EXIST:
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        case AOR_DB_INTERNAL_ERROR:
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_DELETED_SQL_ERROR, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        default:
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_DELETED, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        }

        return true;
    }

    /// Display info on users currently in the realm
    static bool HandleAccountOnlineListCommand (ChatHandler* handler, const char* /*args*/)
    {
        ///- Get the list of accounts ID logged to the realm
        QueryResult resultDB = CharacterDatabase.Query("SELECT name, account, map, zone FROM characters WHERE online > 0");
        if (!resultDB)
        {
            handler->SendSysMessage(LANG_ACCOUNT_LIST_EMPTY);
            return true;
        }

        ///- Display the list of account/characters online
        handler->SendSysMessage(LANG_ACCOUNT_LIST_BAR_HEADER);
        handler->SendSysMessage(LANG_ACCOUNT_LIST_HEADER);
        handler->SendSysMessage(LANG_ACCOUNT_LIST_BAR);

        ///- Cycle through accounts
        do
        {
            Field *fieldsDB = resultDB->Fetch();
            std::string name = fieldsDB[0].GetString();
            uint32 account = fieldsDB[1].GetUInt32();

            ///- Get the username, last IP and GM level of each account
            // No SQL injection. account is uint32.
            QueryResult resultLogin = LoginDatabase.PQuery("SELECT a.username, a.last_ip, aa.gmlevel, a.expansion "
                    "FROM account a "
                    "LEFT JOIN account_access aa "
                    "ON (a.id = aa.id) "
                    "WHERE a.id = '%u'", account);
            if (resultLogin)
            {
                Field *fieldsLogin = resultLogin->Fetch();
                handler->PSendSysMessage(LANG_ACCOUNT_LIST_LINE, fieldsLogin[0].GetCString(), name.c_str(), fieldsLogin[1].GetCString(), fieldsDB[2].GetUInt16(), fieldsDB[3].GetUInt16(), fieldsLogin[3].GetUInt32(), fieldsLogin[2].GetUInt32());
            }
            else
                handler->PSendSysMessage(LANG_ACCOUNT_LIST_ERROR, name.c_str());
        }
        while (resultDB->NextRow());

        handler->SendSysMessage(LANG_ACCOUNT_LIST_BAR);
        return true;
    }

    static bool HandleAccountLockCommand (ChatHandler* handler, const char* args)
    {
        if (!*args)
        {
            handler->SendSysMessage(LANG_USE_BOL);
            handler->SetSentErrorMessage(true);
            return false;
        }

        std::string argstr = (char*) args;
        if (argstr == "on")
        {
            LoginDatabase.PExecute("UPDATE account SET locked = '1' WHERE id = '%d'", handler->GetSession()->GetAccountId());
            handler->PSendSysMessage(LANG_COMMAND_ACCLOCKLOCKED);
            return true;
        }

        if (argstr == "off")
        {
            LoginDatabase.PExecute("UPDATE account SET locked = '0' WHERE id = '%d'", handler->GetSession()->GetAccountId());
            handler->PSendSysMessage(LANG_COMMAND_ACCLOCKUNLOCKED);
            return true;
        }

        handler->SendSysMessage(LANG_USE_BOL);
        handler->SetSentErrorMessage(true);
        return false;
    }

    static bool HandleAccountPasswordCommand (ChatHandler* handler, const char* args)
    {
        if (!*args)
        {
            handler->SendSysMessage(LANG_CMD_SYNTAX);
            handler->SetSentErrorMessage(true);
            return false;
        }

        char *old_pass = strtok((char*) args, " ");
        char *new_pass = strtok(NULL, " ");
        char *new_pass_c = strtok(NULL, " ");

        if (!old_pass || !new_pass || !new_pass_c)
        {
            handler->SendSysMessage(LANG_CMD_SYNTAX);
            handler->SetSentErrorMessage(true);
            return false;
        }

        std::string password_old = old_pass;
        std::string password_new = new_pass;
        std::string password_new_c = new_pass_c;

        if (!sAccountMgr->CheckPassword(handler->GetSession()->GetAccountId(), password_old))
        {
            handler->SendSysMessage(LANG_COMMAND_WRONGOLDPASSWORD);
            handler->SetSentErrorMessage(true);
            return false;
        }

        if (strcmp(new_pass, new_pass_c) != 0)
        {
            handler->SendSysMessage(LANG_NEW_PASSWORDS_NOT_MATCH);
            handler->SetSentErrorMessage(true);
            return false;
        }

        AccountOpResult result = sAccountMgr->ChangePassword(handler->GetSession()->GetAccountId(), password_new);
        switch (result)
        {
        case AOR_OK:
            handler->SendSysMessage(LANG_COMMAND_PASSWORD);
            break;
        case AOR_PASS_TOO_LONG:
            handler->SendSysMessage(LANG_PASSWORD_TOO_LONG);
            handler->SetSentErrorMessage(true);
            return false;
        default:
            handler->SendSysMessage(LANG_COMMAND_NOTCHANGEPASSWORD);
            handler->SetSentErrorMessage(true);
            return false;
        }

        return true;
    }

    static bool HandleAccountCommand (ChatHandler* handler, const char* /*args*/)
    {
        AccountTypes gmlevel = handler->GetSession()->GetSecurity();
        handler->PSendSysMessage(LANG_ACCOUNT_LEVEL, uint32(gmlevel));
        return true;
    }

    /// Set/Unset the expansion level for an account
    static bool HandleAccountSetAddonCommand (ChatHandler* handler, const char *args)
    {
        ///- Get the command line arguments
        char *szAcc = strtok((char*) args, " ");
        char *szExp = strtok(NULL, " ");

        if (!szAcc)
            return false;

        std::string account_name;
        uint32 account_id;

        if (!szExp)
        {
            Player* player = handler->getSelectedPlayer();
            if (!player)
                return false;

            account_id = player->GetSession()->GetAccountId();
            sAccountMgr->GetName(account_id, account_name);
            szExp = szAcc;
        }
        else
        {
            ///- Convert Account name to Upper Format
            account_name = szAcc;
            if (!AccountMgr::normalizeString(account_name))
            {
                handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str());
                handler->SetSentErrorMessage(true);
                return false;
            }

            account_id = sAccountMgr->GetId(account_name);
            if (!account_id)
            {
                handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str());
                handler->SetSentErrorMessage(true);
                return false;
            }
        }

        // Let set addon state only for lesser (strong) security level
        // or to self account
        if (handler->GetSession() && handler->GetSession()->GetAccountId() != account_id && handler->HasLowerSecurityAccount(NULL, account_id, true))
            return false;

        int expansion = atoi(szExp);          //get int anyway (0 if error)
        if (expansion < 0 || uint8(expansion) > sWorld->getIntConfig(CONFIG_EXPANSION))
            return false;

        // No SQL injection
        LoginDatabase.PExecute("UPDATE account SET expansion = '%d' WHERE id = '%u'", expansion, account_id);
        handler->PSendSysMessage(LANG_ACCOUNT_SETADDON, account_name.c_str(), account_id, expansion);
        return true;
    }

    static bool HandleAccountSetGmLevelCommand (ChatHandler* handler, const char *args)
    {
        if (!*args)
            return false;

        std::string targetAccountName;
        uint32 targetAccountId = 0;
        uint32 targetSecurity = 0;
        uint32 gm = 0;
        char* arg1 = strtok((char*) args, " ");
        char* arg2 = strtok(NULL, " ");
        char* arg3 = strtok(NULL, " ");
        bool isAccountNameGiven = true;

        if (arg1 && !arg3)
        {
            if (!handler->getSelectedPlayer())
                return false;
            isAccountNameGiven = false;
        }

        // Check for second parameter
        if (!isAccountNameGiven && !arg2)
            return false;

        // Check for account
        if (isAccountNameGiven)
        {
            targetAccountName = arg1;
            if (!AccountMgr::normalizeString(targetAccountName))
            {
                handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, targetAccountName.c_str());
                handler->SetSentErrorMessage(true);
                return false;
            }
        }

        // Check for invalid specified GM level.
        gm = (isAccountNameGiven) ? atoi(arg2) : atoi(arg1);
        if (gm > SEC_CONSOLE)
        {
            handler->SendSysMessage(LANG_BAD_VALUE);
            handler->SetSentErrorMessage(true);
            return false;
        }

        // handler->getSession() == NULL only for console
        targetAccountId = (isAccountNameGiven) ? sAccountMgr->GetId(targetAccountName) : handler->getSelectedPlayer()->GetSession()->GetAccountId();
        int32 gmRealmID = (isAccountNameGiven) ? atoi(arg3) : atoi(arg2);
        uint32 plSecurity;
        if (handler->GetSession())
            plSecurity = sAccountMgr->GetSecurity(handler->GetSession()->GetAccountId(), gmRealmID);
        else
            plSecurity = SEC_CONSOLE;

        // can set security level only for target with less security and to less security that we have
        // This is also reject self apply in fact
        targetSecurity = sAccountMgr->GetSecurity(targetAccountId, gmRealmID);
        if (targetSecurity >= plSecurity || gm >= plSecurity)
        {
            handler->SendSysMessage(LANG_YOURS_SECURITY_IS_LOW);
            handler->SetSentErrorMessage(true);
            return false;
        }

        // Check and abort if the target gm has a higher rank on one of the realms and the new realm is -1
        if (gmRealmID == -1)
        {
            QueryResult result = LoginDatabase.PQuery("SELECT * FROM account_access WHERE id = '%u' AND gmlevel > '%d'", targetAccountId, gm);
            if (result)
            {
                handler->SendSysMessage(LANG_YOURS_SECURITY_IS_LOW);
                handler->SetSentErrorMessage(true);
                return false;
            }
        }

        // Check if provided realmID has a negative value other than -1
        if (gmRealmID < -1)
        {
            handler->SendSysMessage(LANG_INVALID_REALMID);
            handler->SetSentErrorMessage(true);
            return false;
        }

        // If gmRealmID is -1, delete all values for the account id, else, insert values for the specific realmID
        if (gmRealmID == -1)
            LoginDatabase.PExecute("DELETE FROM account_access WHERE id = '%u'", targetAccountId);
        else
            LoginDatabase.PExecute("DELETE FROM account_access WHERE id = '%u' AND (RealmID = '%d' OR RealmID = '-1')", targetAccountId, realmID);

        if (gm != 0)
            LoginDatabase.PExecute("INSERT INTO account_access VALUES ('%u', '%d', '%d')", targetAccountId, gm, realmID);
        handler->PSendSysMessage(LANG_YOU_CHANGE_SECURITY, targetAccountName.c_str(), gm);
        return true;
    }

    /// Set password for account
    static bool HandleAccountSetPasswordCommand (ChatHandler* handler, const char *args)
    {
        if (!*args)
            return false;

        ///- Get the command line arguments
        char *szAccount = strtok((char*) args, " ");
        char *szPassword1 = strtok(NULL, " ");
        char *szPassword2 = strtok(NULL, " ");

        if (!szAccount || !szPassword1 || !szPassword2)
            return false;

        std::string account_name = szAccount;
        if (!AccountMgr::normalizeString(account_name))
        {
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        }

        uint32 targetAccountId = sAccountMgr->GetId(account_name);
        if (!targetAccountId)
        {
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        }

        /// can set password only for target with less security
        /// This is also reject self apply in fact
        if (handler->HasLowerSecurityAccount(NULL, targetAccountId, true))
            return false;

        if (strcmp(szPassword1, szPassword2))
        {
            handler->SendSysMessage(LANG_NEW_PASSWORDS_NOT_MATCH);
            handler->SetSentErrorMessage(true);
            return false;
        }

        AccountOpResult result = sAccountMgr->ChangePassword(targetAccountId, szPassword1);

        switch (result)
        {
        case AOR_OK:
            handler->SendSysMessage(LANG_COMMAND_PASSWORD);
            break;
        case AOR_NAME_NOT_EXIST:
            handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, account_name.c_str());
            handler->SetSentErrorMessage(true);
            return false;
        case AOR_PASS_TOO_LONG:
            handler->SendSysMessage(LANG_PASSWORD_TOO_LONG);
            handler->SetSentErrorMessage(true);
            return false;
        default:
            handler->SendSysMessage(LANG_COMMAND_NOTCHANGEPASSWORD);
            handler->SetSentErrorMessage(true);
            return false;
        }
        return true;
    }
};

void AddSC_account_commandscript ()
{
    new account_commandscript();
}
